/**
 * Copyright (C) 2021-2022 maminjie <canpool@163.com>
 * SPDX-License-Identifier: LGPL-2.1
 * Powered by Qt-Advanced-Docking-System
**/

#include "dockfloatingdragpreview.h"

#include "dockpannel.h"
#include "dockcontainer.h"
#include "dockmanager.h"
#include "dockoverlay.h"
#include "dockwidget.h"

#include <QApplication>
#include <QEvent>
#include <QKeyEvent>
#include <QLoggingCategory>
#include <QPainter>

#include <iostream>

QADS_BEGIN_NAMESPACE

class DockFloatingDragPreviewPrivate
{
public:
    DockFloatingDragPreviewPrivate(DockFloatingDragPreview *parent);

    void updateDropOverlays(const QPoint &globalPos);

    void setHidden(bool value);
    void cancelDragging();
    void createFloatingWidget();

public:
    DockFloatingDragPreview *q;
    DockManager *m_dockManager = nullptr;
    DockContainer *m_dropContainer = nullptr;
    DockPannel *m_contentSourcePannel = nullptr;
    QWidget *m_content = nullptr;
    QPoint m_dragStartPos;
    QPixmap m_contentPreviewPixmap;
    bool m_hidden = false;
    bool m_canceled = false;
}; // class DockFloatingDragPreviewPrivate

DockFloatingDragPreviewPrivate::DockFloatingDragPreviewPrivate(DockFloatingDragPreview *parent)
    : q(parent)
{}

void DockFloatingDragPreviewPrivate::updateDropOverlays(const QPoint &globalPos)
{
    if (!q->isVisible() || !m_dockManager) {
        return;
    }

    auto containers = m_dockManager->dockContainers();
    DockContainer *topContainer = nullptr;

    for (auto containerWidget : qAsConst(containers)) {
        if (!containerWidget->isVisible()) {
            continue;
        }

        const QPoint mappedPosition = containerWidget->mapFromGlobal(globalPos);
        if (containerWidget->rect().contains(mappedPosition)) {
            if (!topContainer || containerWidget->isFrontOf(topContainer)) {
                topContainer = containerWidget;
            }
        }
    }

    m_dropContainer = topContainer;
    auto containerOverlay = m_dockManager->containerOverlay();
    auto pannelOverlay = m_dockManager->pannelOverlay();
    auto pannelDropArea = pannelOverlay->dropAreaUnderCursor();
    auto containerDropArea = containerOverlay->dropAreaUnderCursor();

    if (!topContainer) {
        containerOverlay->hideOverlay();
        pannelOverlay->hideOverlay();

        if (QAds::testConfigFlag(QAds::DragPreviewIsDynamic)) {
            setHidden(false);
        }
        return;
    }

    const int visiblePannelCnt = topContainer->visibleDockPannelCount();
    containerOverlay->setAllowedAreas(visiblePannelCnt > 1 ? OuterDockAreas : AllDockAreas);
    auto pannel = topContainer->dockPannelAt(globalPos);

    if (pannel && pannel->isVisible() && visiblePannelCnt >= 0 && pannel != m_contentSourcePannel) {
        pannelOverlay->enableDropPreview(true);
        pannelOverlay->setAllowedAreas((visiblePannelCnt == 1) ? NoDockWidgetArea : pannel->allowedAreas());
        DockWidgetArea area = pannelOverlay->showOverlay(pannel);

        // A CenterDockWidgetArea for the pannelOverlay() indicates that the mouse is in the
        // title bar. If the ContainerArea is valid then we ignore the dock area of the
        // pannelOverlay() and disable the drop preview
        if ((area == CenterDockWidgetArea) && (containerDropArea != InvalidDockWidgetArea)) {
            pannelOverlay->enableDropPreview(false);
            containerOverlay->enableDropPreview(true);
        } else {
            containerOverlay->enableDropPreview(InvalidDockWidgetArea == area);
        }
        containerOverlay->showOverlay(topContainer);
    } else {
        pannelOverlay->hideOverlay();

        // If there is only one single visible dock area in a container, then
        // it does not make sense to show a dock overlay because the dock area
        // would be removed and inserted at the same position
        if (visiblePannelCnt <= 1) {
            containerOverlay->hideOverlay();
        } else {
            containerOverlay->showOverlay(topContainer);
        }

        if (pannel == m_contentSourcePannel && InvalidDockWidgetArea == containerDropArea) {
            m_dropContainer = nullptr;
        }
    }

    if (QAds::testConfigFlag(QAds::DragPreviewIsDynamic)) {
        setHidden(pannelDropArea != InvalidDockWidgetArea || containerDropArea != InvalidDockWidgetArea);
    }
}

void DockFloatingDragPreviewPrivate::setHidden(bool value)
{
    m_hidden = value;
    q->update();
}

/**
 * Cancel dragging and emit the draggingCanceled event
 */
void DockFloatingDragPreviewPrivate::cancelDragging()
{
    m_canceled = true;
    emit q->draggingCanceled();
    m_dockManager->containerOverlay()->hideOverlay();
    m_dockManager->pannelOverlay()->hideOverlay();
    q->close();
}

/**
 * Creates the real floating widget in case the mouse is released outside
 * outside of any drop area
 */
void DockFloatingDragPreviewPrivate::createFloatingWidget()
{
    DockWidget *dockWidget = qobject_cast<DockWidget *>(m_content);
    DockPannel *pannel = qobject_cast<DockPannel *>(m_content);
    DockFloatingWidget *floatingWidget = nullptr;

    if (dockWidget && dockWidget->features().testFlag(DockWidget::DockWidgetFloatable)) {
        floatingWidget = new DockFloatingWidget(dockWidget);
    } else if (pannel && pannel->features().testFlag(DockWidget::DockWidgetFloatable)) {
        floatingWidget = new DockFloatingWidget(pannel);
    }

    if (floatingWidget) {
        floatingWidget->setGeometry(q->geometry());
        floatingWidget->show();

        if (!QAds::testConfigFlag(QAds::DragPreviewHasWindowFrame)) {
            QApplication::processEvents();
            int frameHeight = floatingWidget->frameGeometry().height() - floatingWidget->geometry().height();
            QRect fixedGeometry = q->geometry();
            fixedGeometry.adjust(0, frameHeight, 0, 0);
            floatingWidget->setGeometry(fixedGeometry);
        }
    }
}

DockFloatingDragPreview::DockFloatingDragPreview(QWidget *content, QWidget *parent)
    : QWidget(parent)
    , d(new DockFloatingDragPreviewPrivate(this))
{
    d->m_content = content;
    setAttribute(Qt::WA_DeleteOnClose);

    if (QAds::testConfigFlag(QAds::DragPreviewHasWindowFrame)) {
        setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
    } else {
        setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
        setAttribute(Qt::WA_NoSystemBackground);
        setAttribute(Qt::WA_TranslucentBackground);
    }

#if defined(Q_OS_LINUX)
    auto flags = windowFlags();
    flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint;
    setWindowFlags(flags);
#endif
    setWindowOpacity(0.6);

    // Create a static image of the widget that should get undocked
    // This is like some kind preview image like it is uses in drag and drop operations
    if (QAds::testConfigFlag(QAds::DragPreviewShowsContentPixmap)) {
        d->m_contentPreviewPixmap = QPixmap(content->size());
        content->render(&d->m_contentPreviewPixmap);
    }

    connect(qApp, &QApplication::applicationStateChanged, this, &DockFloatingDragPreview::onApplicationStateChanged);
    // The only safe way to receive escape key presses is to install an event
    // filter for the application object
    QApplication::instance()->installEventFilter(this);
}

DockFloatingDragPreview::DockFloatingDragPreview(DockWidget *content)
    : DockFloatingDragPreview(static_cast<QWidget *>(content), content->dockManager()) // TODO static_cast?
{
    d->m_dockManager = content->dockManager();

    if (content->dockPannel()->openDockWidgetsCount() == 1) {
        d->m_contentSourcePannel = content->dockPannel();
    }

    setWindowTitle(content->windowTitle());
}

DockFloatingDragPreview::DockFloatingDragPreview(DockPannel *content)
    : DockFloatingDragPreview(static_cast<QWidget *>(content), content->dockManager()) // TODO static_cast?
{
    d->m_dockManager = content->dockManager();
    d->m_contentSourcePannel = content;
    setWindowTitle(content->currentDockWidget()->windowTitle());
}

DockFloatingDragPreview::~DockFloatingDragPreview()
{
    delete d;
}

void DockFloatingDragPreview::startFloating(const QPoint &dragStartPos, const QSize &size,
                                            DragState dragState, QWidget *mouseEventHandler)
{
    Q_UNUSED(mouseEventHandler)
    Q_UNUSED(dragState)
    resize(size);
    d->m_dragStartPos = dragStartPos;
    moveFloating();
    show();
}

void DockFloatingDragPreview::moveFloating()
{
    const int borderSize = (frameSize().width() - size().width()) / 2;
    const QPoint moveToPos = QCursor::pos() - d->m_dragStartPos - QPoint(borderSize, 0);
    move(moveToPos);
    d->updateDropOverlays(QCursor::pos());
}

void DockFloatingDragPreview::finishDragging()
{
    auto pannelDropArea = d->m_dockManager->pannelOverlay()->visibleDropAreaUnderCursor();
    auto containerDropArea = d->m_dockManager->containerOverlay()->visibleDropAreaUnderCursor();

    if (!d->m_dropContainer) {
        d->createFloatingWidget();
    } else if (pannelDropArea != InvalidDockWidgetArea) {
        d->m_dropContainer->dropWidget(d->m_content, pannelDropArea, d->m_dropContainer->dockPannelAt(QCursor::pos()));
    } else if (containerDropArea != InvalidDockWidgetArea) {
        // If there is only one single dock area, and we drop into the center
        // then we tabify the dropped widget into the only visible dock area
        if (d->m_dropContainer->visibleDockPannelCount() <= 1 && CenterDockWidgetArea == containerDropArea)
            d->m_dropContainer->dropWidget(d->m_content, containerDropArea, d->m_dropContainer->dockPannelAt(QCursor::pos()));
        else {
            d->m_dropContainer->dropWidget(d->m_content, containerDropArea, nullptr);
        }
    } else {
        d->createFloatingWidget();
    }

    this->close();
    d->m_dockManager->containerOverlay()->hideOverlay();
    d->m_dockManager->pannelOverlay()->hideOverlay();
}

void DockFloatingDragPreview::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    if (d->m_hidden) {
        return;
    }

    QPainter painter(this);

    if (QAds::testConfigFlag(QAds::DragPreviewShowsContentPixmap)) {
        painter.drawPixmap(QPoint(0, 0), d->m_contentPreviewPixmap);
    }

    // If we do not have a window frame then we paint a QRubberBand like frameless window
    if (!QAds::testConfigFlag(QAds::DragPreviewHasWindowFrame)) {
        QColor color = palette().color(QPalette::Active, QPalette::Highlight);
        QPen pen = painter.pen();
        pen.setColor(color.darker(120));
        pen.setStyle(Qt::SolidLine);
        pen.setWidth(1);
        pen.setCosmetic(true);
        painter.setPen(pen);
        color = color.lighter(130);
        color.setAlpha(64);
        painter.setBrush(color);
        painter.drawRect(rect().adjusted(0, 0, -1, -1));
    }
}

bool DockFloatingDragPreview::eventFilter(QObject *watched, QEvent *event)
{
    if (!d->m_canceled && event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);

        if (keyEvent->key() == Qt::Key_Escape) {
            watched->removeEventFilter(this);
            d->cancelDragging();
        }
    }

    return false;
}

void DockFloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state)
{
    if (state != Qt::ApplicationActive) {
        disconnect(qApp, &QApplication::applicationStateChanged,
                   this, &DockFloatingDragPreview::onApplicationStateChanged);
        d->cancelDragging();
    }
}

QADS_END_NAMESPACE
